java NIO.

阻塞和非阻塞

  阻塞是函数没有返回结果就一直等待,知道得到返回结果。非阻塞则是函数没有返回结果自己不用等待可以去干别的事情,这是函数内部的的实现区别,也就是函数未就绪时是直接返回结果。   

同步和异步

  同步是指主动请求并且等待结果。当数据就绪后在读写时必须阻塞,异步则是请求到数据后可以继续处理其他任务,随后等待结果,这可以使得进程读写时不阻塞。

小结

  所以说,阻塞,非阻塞和同步,异步是不一样的概念,阻塞和非阻塞是函数的实现方式就是未就绪时是否直接返回结果,阻塞和非阻塞在程序中也算是同步的,而异步则是不管是不是直接返回结果,我都可以干别的事,等到返回结果再处理。

java NIO

  java的最初io流是阻塞的BIO流,不适合于多连接的处理情况。因为多个连接就要多个线程。而NIO的出现解决了这个问题。而且NIO的操作方式类似于操作系统,因此效率会更加高一些。
  io的操作是基于字节流和字符流,是单向的比如inputStreanm,只能读,而NIO则是基于Channel和Buffer,是双向,比如Channel是双向的既可以用来读也可以来写。
  NIO是非阻塞的,IO则是阻塞的,一个线程调用read()方法时,如果没有操作完,该线程就会一直阻塞。对于这个线程而言,这时他不能干别的事了,因为程序时顺序执行的。NIO则是不一样的,无论是读取数据还是写入数据都不会使得这个线程阻塞下去。可以继续干自己的事情。

代码示例

  直接代码示例吧,具体的Channel,Buffer,Selector的用法可以查询java官方api。

  这里直接上几个代码案例,直接对比。


public class Test {

public static void main(String[] args) throws Exception {
    File file = new File("data.txt");
    FileInputStream fileOutputStream = new FileInputStream(file);
    FileChannel fileChannel = fileOutputStream.getChannel();
    ByteBuffer byteBuffer = ByteBuffer.allocate(10);
    // String string = "nio test";
    int a = fileChannel.read(byteBuffer);
    System.out.println(a);

    while (a != -1) {
        byteBuffer.flip();
        while (byteBuffer.hasRemaining()) {
            System.out.print((char) byteBuffer.get());
        }
        byteBuffer.compact();
        a = fileChannel.read(byteBuffer);
    }
}

}

  这个代码是读取一个文件,并且运用NIO的方式输出出来。有了大概的印象,那么我们来看看在socket中,这些操作时怎么处理的,非阻塞机制到底怎么提高了这个的优化效率。
  第一个是io的服务器


public class Snippet {
public static void main(String[] args) {
ServerSocket serverSocket = null;
InputStream in = null;
try {
serverSocket = new ServerSocket(8088);
int recvMsgSize = 0;
byte[] recvBuf = new byte[1024];
while (true) {
Socket clntSocket = serverSocket.accept();
SocketAddress clientAddress = clntSocket.getRemoteSocketAddress();
System.out.println(“Handling client at “ + clientAddress);
in = clntSocket.getInputStream();
while ((recvMsgSize = in.read(recvBuf)) != -1) {
byte[] temp = new byte[recvMsgSize];
// System.arraycopy(recvBuf, 0, temp, 0, recvMsgSize);
System.out.println(new String(temp));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (serverSocket != null) {
serverSocket.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}


  客户端代码直接用NIO的模式写吧


public class NioClient {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress(“127.0.0.1”, 8088));
System.out.println(“aaa”);

        if (socketChannel.finishConnect()) {
            int i = 0;
            while (i<10) {
                TimeUnit.SECONDS.sleep(1);
                String info = "I'm " + i++ + "-th information from client";
                buffer.clear();
                buffer.put(info.getBytes());
                buffer.flip();
                while (buffer.hasRemaining()) {// 判断是否数据读完
                    System.out.println(buffer);
                    socketChannel.write(buffer);
                }
            }
        }
    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
    } finally {
        try {
            if (socketChannel != null) {
                socketChannel.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

}


  假设服务器是这种代码,客户端有多个连接到来,那么一次只能连接一个,直到用完,或许有的小伙伴说,多线程啊,每个线程一个连接,线程池管理。恩,但是线程太消耗资源,而且线程转换也会消耗资源。那么nio来了,我们来看看Nio的实现。

public class ServerConnect
{
    private static final int BUF_SIZE=1024;
    private static final int PORT = 8080;
    private static final int TIMEOUT = 3000;

    public static void main(String[] args)
    {
        selector();
    }

    public static void handleAccept(SelectionKey key) throws IOException{
        ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();
        SocketChannel sc = ssChannel.accept();
        sc.configureBlocking(false);
        sc.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocateDirect(BUF_SIZE));
    }

    public static void handleRead(SelectionKey key) throws IOException{
        SocketChannel sc = (SocketChannel)key.channel();
        ByteBuffer buf = (ByteBuffer)key.attachment();
        long bytesRead = sc.read(buf);
        while(bytesRead>0){
            buf.flip();
            while(buf.hasRemaining()){
                System.out.print((char)buf.get());
            }
            System.out.println();
            buf.clear();
            bytesRead = sc.read(buf);
        }
        if(bytesRead == -1){
            sc.close();
        }
    }

    public static void handleWrite(SelectionKey key) throws IOException{
        ByteBuffer buf = (ByteBuffer)key.attachment();
        buf.flip();
        SocketChannel sc = (SocketChannel) key.channel();
        while(buf.hasRemaining()){
            sc.write(buf);
        }
        buf.compact();
    }

    public static void selector() {
        Selector selector = null;
        ServerSocketChannel ssc = null;
        try{
            selector = Selector.open();
            ssc= ServerSocketChannel.open();
            ssc.socket().bind(new InetSocketAddress(PORT));
            ssc.configureBlocking(false);
            ssc.register(selector, SelectionKey.OP_ACCEPT);

            while(true){
                if(selector.select(TIMEOUT) == 0){
                    System.out.println("==");
                    continue;
                }
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while(iter.hasNext()){
                    SelectionKey key = iter.next();
                    if(key.isAcceptable()){
                        handleAccept(key);
                    }
                    if(key.isReadable()){
                        handleRead(key);
                    }
                    if(key.isWritable() && key.isValid()){
                        handleWrite(key);
                    }
                    if(key.isConnectable()){
                        System.out.println("isConnectable = true");
                    }
                    iter.remove();
                }
            }

        }catch(IOException e){
            e.printStackTrace();
        }finally{
            try{
                if(selector!=null){
                    selector.close();
                }
                if(ssc!=null){
                    ssc.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
}

Selector运行单线程处理多个Channel,如果你的应用打开了多个通道,但每个连接的流量都很低,使用Selector就会很方便。例如在一个聊天服务器中。要使用Selector, 得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新的连接进来、数据接收。
  我们在上面的代码里面将ServerSocketChannel注册到Selector里面,接受accept事件,每当有一个连接到来,select()就会有返回值,因为这个事件完成了,就会触发下面的操作,然后我们把这个新加入的连接也放到selector里面,等待读事件,或者写事件,当这个事件准备好了,就会启动返回。